package com.atguigu.userprofile.service.impl;

import com.alibaba.fastjson.JSON;
import com.atguigu.userprofile.bean.TagCondition;
import com.atguigu.userprofile.bean.TagInfo;
import com.atguigu.userprofile.bean.UserGroup;
import com.atguigu.userprofile.constants.ConstCodes;
import com.atguigu.userprofile.mapper.UserGroupMapper;
import com.atguigu.userprofile.service.TagInfoService;
import com.atguigu.userprofile.service.UserGroupService;
import com.atguigu.userprofile.utils.RedisUtil;
import com.baomidou.dynamic.datasource.annotation.DS;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;

import org.apache.catalina.User;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import redis.clients.jedis.Jedis;

import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * <p>
 * 服务实现类
 * </p>
 *
 * @author zhangchen
 * @since 2021-05-04
 */
@Service
@Slf4j
@DS("mysql")
public class UserGroupServiceImpl extends ServiceImpl<UserGroupMapper, UserGroup> implements UserGroupService {


    @Override
    public void writeUserGroupToMysql(UserGroup userGroup) {

        //补充信息
        String tagConditionsStr = JSON.toJSONString(userGroup.getTagConditions());
        userGroup.setConditionJsonStr(tagConditionsStr);

        userGroup.setCreateTime(new Date());

        userGroup.setConditionComment(userGroup.conditionJsonToComment());

        //存入Mysql
        save(userGroup);

    }

    @Override
    public void saveUserGroupToCk(UserGroup userGroup) {

        String bitmapAndSql = combineConditions(userGroup);

        baseMapper.executeUserGroup(userGroup.getId().toString(),bitmapAndSql);

    }

    @Override
    public UserGroup saveUserGroupToRedis(UserGroup userGroup) {

        //查询ck中的人群信息
        List<String> users = baseMapper.queryUsersFromCKByUserGroupId(userGroup.getId().toString());

        String [] value = new String [] {};
        value = users.toArray(value);

        //写入redis
        //获取链接
        Jedis jedis = RedisUtil.getJedis();

        /*
            执行写入
                K:  字符串，唯一标识. 和 user_group_id相关
                V：  五种类型。
                        单值: string,hash
                        多值: set,zset,list,hash
                            set的api中有 sismember(xx): 快速判断某个人是否在当前的人群包中
         */
        String key = "user_group_" + userGroup.getId();
        //先删除旧的数据
        jedis.del(key);
        jedis.sadd(key,value);

        //关闭连接,还回池中
        jedis.close();

        userGroup.setUserGroupNum(users.size()+0l);

        return userGroup;
    }

    @Override
    public Integer evaluateUserGroupNum(UserGroup userGroup) {

        //根据UserGroup生成查询条件
        String bitmapAndSql = combineConditions(userGroup);

        Integer userGroupNum = baseMapper.evaluateUserGroupNum(bitmapAndSql);

        return userGroupNum;
    }

    @Override
    public void updateUserGroup(UserGroup userGroup) {

        //先删除之前的人群包
        baseMapper.deleteUserGroupById(userGroup.getId().toString());
        //再写入新的
        saveUserGroupToCk(userGroup);

    }

    @Autowired
    private TagInfoService tagInfoService;
    /*
            将多个条件组合
        bitmapAnd(
              bitmapAnd(
                (sql1),(sql2)
              ),
              (sql3)
         )
     */
    public String combineConditions(UserGroup userGroup){

        //获取用户的条件
        List<TagCondition> tagConditions = userGroup.getTagConditions();

        //获取业务日期
        String doDate = userGroup.getBusiDate();

        //获取所有 标签的TagInfo
        Map<String, TagInfo> info = tagInfoService.getTagInfoMapWithCode();

        //准备字符串拼接最终结果
        StringBuilder result = new StringBuilder();

        for (TagCondition tagCondition : tagConditions) {


            String sql = generateSingeConditionSql(tagCondition, info, doDate);
            //第一个条件，必须在外层拼接 ()
            if (result.length() == 0){
                result.append("(").append(sql).append(")");
            }else{
                //  bitmapAnd( bitmapAnd((sql1),( sql2)),(sql3))
                result.insert(0,"bitmapAnd(")
                      .append(",(")
                      .append(sql)
                      .append("))");

            }
        }

        return result.toString();

    }

    /*
        生成单个条件对应的sql

   select
        groupBitmapMergeState(us)
    from ①user_tag_value_string
    where tag_code = ②'tag_population_attributes_nature_period'
     and tag_value ③in  ④('00后', '10后')
     and dt = ⑤'2020-06-14'


     变量部分：
        ①需要根据查询条件中的tagCode的 tagValueType 确定从哪张表查询
                举例:tag_consume_behavior_order_7damount 从 user_tag_value_decimal
                    tag_population_attributes_nature_gender 从 user_tag_value_string

                 条件中，只有标签名字，没有标签值的类型。
                    根据标签的名字获取标签的信息。
                    Map<String,TagInfo> info
                        k: 标签名
                        v: TagInfo

        ②获取查询条件中的tagCode
        ③操作符号
                包含 :  in
                大于 :  >
        ④查询标签的条件值
                多个，需要两两间隔 逗号
                查询的标签值是字符串类型，还需要为每个参数拼接 引号
         ⑤业务日期

     */
    public String generateSingeConditionSql(TagCondition tagCondition,Map<String,TagInfo> info,String doDate){

        String template = "  select groupBitmapMergeState(us) from %s  where tag_code = '%s'  " +
                          "  and tag_value %s %s  and dt = '%s' ";

        //需要根据查询条件中的tagCode的 tagValueType 确定从哪张表查询
        TagInfo tagInfo = info.get(tagCondition.getTagCode());
        String sourceTable = "";
        String tagValueType = tagInfo.getTagValueType();
        switch (tagValueType){
            case ConstCodes.TAG_VALUE_TYPE_LONG: sourceTable = "user_tag_value_long"; break;
            case ConstCodes.TAG_VALUE_TYPE_DECIMAL:  sourceTable = "user_tag_value_decimal"; break;
            case ConstCodes.TAG_VALUE_TYPE_STRING:  sourceTable = "user_tag_value_string"; break;
            case ConstCodes.TAG_VALUE_TYPE_DATE:  sourceTable = "user_tag_value_date"; break;
        }

        //获取运算符
        String operator = getConditionOperator(tagCondition.getOperator());

        //生成条件值部分
        String valueStr = "";
        if (ConstCodes.TAG_VALUE_TYPE_STRING.equals(tagValueType)
            ||
            ConstCodes.TAG_VALUE_TYPE_DATE.equals(tagValueType)
        ){
            //  00后','10后','90后
            valueStr =  "'" + StringUtils.join(tagCondition.getTagValues(),"','") + "'";
        }else{
            // 200，300，400
            valueStr =   StringUtils.join(tagCondition.getTagValues(),",");
        }

        //运算符是 in 或 nin 属于范围查询，需要在条件外层拼接 ()
        if ("in".equals(tagCondition.getOperator()) || "nin".equals(tagCondition.getOperator())){
            valueStr = "(" + valueStr + ")";
        }

        //格式化
        String sql = String.format(template, sourceTable, tagCondition.getTagCode().toLowerCase(), operator, valueStr, doDate);

        return sql;


    }

    private String getConditionOperator(String operator){

        switch (operator) {
            case "eq" : return "=";
            case "neq" : return "<>";
            case "gte" : return ">=";
            case "lte" : return "<=";
            case "gt" : return ">";
            case "lt" : return "<";
            case "in" : return "in";
            case "nin" : return "not in";
        }
        throw new RuntimeException("没有匹配的操作符!");

    }

}
